#pragma once
#include <QTcpServer>
#include <QTcpSocket>
#include <QDebug>
#include "QUtil.h"

static const int SERVER_PORT = 12345;
static const QString SERVER_IP = "172.16.17.206";

class QSocketServer : public QTcpServer {
	Q_OBJECT
public:
	enum Feature {
		ALC = 0x01, LDW = 0x02, DCLC = 0x03, ACC_TG = 0x04, LampBrake = 0x05, LampTurn = 0x06, ZhicheDoor = 0x07, ZhicheSpeed = 0x08,
		SQ_ACC = 0x10, SQ_FCW = 0x11, SQ_NOA = 0x12, SQ_ALC = 0x13, SQ_ACAS = 0x14,
		Camera_PosX = 0x21, Camera_PosY = 0x22, Camera_PosZ = 0x23, Camera_PitchAngle = 0x24, Camera_Fov = 0x25, Camera_YawAngle = 0x26, Camera_ViewScale = 0x27,
		Camera_PosX_Reset = 0x31, Camera_PosY_Reset = 0x32, Camera_PosZ_Reset = 0x33, Camera_PitchAngle_Reset = 0x34, Camera_Fov_Reset = 0x35, 
		Camera_YawAngle_Reset = 0x36, Camera_ViewScale_Reset = 0x37, Camera_All_Reset = 0x38
	};
	enum ValueType { Int = 0x01, Float = 0x02 };

	enum { TimeoutMs = 1000 };

	QSocketServer(QObject *parent = nullptr) : QTcpServer(parent) {}

	void listenToHost(const std::function<void(int, int, float)>& callback = [](int, int, float){}, int localPORT = SERVER_PORT, const QString& localIP = SERVER_IP) {
		QHostAddress hostAddr;
		if (localIP.isEmpty())
			QUtil::getLocalIP(&hostAddr);
		else
			hostAddr = localIP;
		m_callback = callback;
		bool success = this->listen(hostAddr, localPORT);
		if (success)
			qDebug() << "Server started, listening ip:port = " << this->serverAddress().toString() << ":" << this->serverPort();
		else
			qWarning() << "Unable to start server:" << this->errorString();
	}
protected:
	void incomingConnection(qintptr socketDescriptor) override {
		m_tcpSocket = new QTcpSocket(this);
		if (!m_tcpSocket->setSocketDescriptor(socketDescriptor)) {
			qCritical() << "Unable to accept client connection:" << m_tcpSocket->errorString();
			m_tcpSocket->deleteLater();
			return;
		}
		qInfo() << "Client connected:" << m_tcpSocket->peerAddress().toString() << ":" << m_tcpSocket->peerPort();

		QAbstractSocket::connect(m_tcpSocket, &QAbstractSocket::readyRead, [&] {
			if(m_tcpSocket->bytesAvailable() < sizeof(qint8) * 2 + sizeof(int))
				return;
			QDataStream in(m_tcpSocket);
			qint8 feature, valueType;
			in >> feature >> valueType;
			int intValue;
			float floatValue;
			bool isFloatType = ValueType::Float == valueType;
			if (isFloatType) {
				in >> floatValue;
				qInfo() << "RECV:[" << floatValue << "]";
			} else {
				in >> intValue;
				qInfo() << "RECV:[" << intValue << "]";
			}

			QByteArray bytes;
			QDataStream out(&bytes, QIODevice::WriteOnly);
			out << feature << valueType;
			if (isFloatType)
				out << floatValue;
			else
				out << intValue;
			m_tcpSocket->write(bytes);
			m_tcpSocket->flush();

			bool status = m_tcpSocket->waitForBytesWritten(TimeoutMs);
			/**@note data may have been sent (ignore `UnknownError`)*/
			if (status || m_tcpSocket->error() == QAbstractSocket::UnknownSocketError) {
				if (isFloatType) {
					m_callback(feature, -1, floatValue);
					qInfo() << "RESP:[" << floatValue << "]";
				} else {
					m_callback(feature, intValue, -1);
					qInfo() << "RESP:[" << intValue << "]";
				}
			} else {
				qCritical() << "Socket state:" << m_tcpSocket->state()
							<< "; Socket error:" << m_tcpSocket->error()
							<< "; Socket description:" << m_tcpSocket->errorString();
			}
		});
		
		QAbstractSocket::connect(m_tcpSocket, &QAbstractSocket::disconnected, [&] {
			qWarning() << "The client has been disconnected:" << m_tcpSocket->peerAddress() << ":" << m_tcpSocket->peerPort();
		});
		QAbstractSocket::connect(m_tcpSocket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), [&] {
			qWarning() << "Socket error:" << m_tcpSocket->errorString();
		});
	}
private:
	QTcpSocket* m_tcpSocket;
	std::function<void(int, int, float)> m_callback;
};